package com.espirit.smc.server;
import static com.espirit.util.Constant.FORMATTER;
import static com.espirit.util.Constant.TIMESTAMP_FORMATTER;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.io.IOUtils;
import org.apache.http.Consts;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.CollectionType;
import org.codehaus.jackson.map.type.SimpleType;

import com.espirit.file.CompressFileUtil;
import com.espirit.file.OperateKind;
import com.espirit.smc.CompareResult;

public class Server {
	
	private static final FileSystem fs = FileSystems.getDefault();
	
	private ServerProperties properties;
	
	public Server(ServerProperties properties) {
		this.properties = properties;
	}

	public static Map<String, Instant> parseFileList(String filePath) throws IOException{
		return parseFileList(fs.getPath(filePath)); 
	}

	public static Map<String, Instant> parseFileList(Path path) throws IOException {
		Map<String, Instant> map = new ConcurrentHashMap<String, Instant>();
		try(FileReader fr = new FileReader(path.toFile());
			BufferedReader br = new BufferedReader(fr)) {
			String line;
			while ((line = br.readLine())!=null) {
				String[] parts = line.split("\t");
				Instant instant = Instant.from(FORMATTER.parse(parts[1]));
				map.put(parts[0].replace("\\", "/"), instant);
			}
		}
		return map;
	}
	
	public List<CompareResult> loadDiffrentFileList(String filePath) throws IOException, InterruptedException, ExecutionException, TimeoutException{
		return loadDiffrentFileList(fs.getPath(filePath));
	}

	public List<CompareResult> loadDiffrentFileList(Path path) throws IOException, InterruptedException, ExecutionException, TimeoutException {
		Path rootPath = properties.getRootPath();
		Map<String, Instant> fileList = parseFileList(path);
		return loadDiffrenFileList(fileList,rootPath,properties.getWatchPaths(),properties.getIgnoreSubDirPaths());
	}
	
	public static List<CompareResult>  loadDiffrenFileList(Map<String, Instant> fileList, Path rootPath, Set<Path> watchPaths,Set<Path> ignorePaths)throws IOException, InterruptedException, ExecutionException, TimeoutException  {
		List<Future<List<CompareResult>>> futures = new LinkedList<Future<List<CompareResult>>>();
		ExecutorService es = Executors.newCachedThreadPool();
		for (Path p : watchPaths) {
			if (ignorePaths!=null && ignorePaths.contains(p)) {
				futures.add(es.submit(new CompareTask(rootPath,p,fileList,Boolean.TRUE)));
			}else{
				futures.add(es.submit(new CompareTask(rootPath,p,fileList)));
			}
		}
		List<CompareResult> crs = new LinkedList<CompareResult>();
		for (Future<List<CompareResult>> future: futures) {
			List<CompareResult> results  = future.get(2l,TimeUnit.MINUTES);
			for (CompareResult result : results) {
				String p = result.getPath();
				if (fileList.containsKey(p)) {
					fileList.remove(p);
				}
				if (OperateKind.UNCHAGE != result.getKind() && OperateKind.OUTDATED != result.getKind()) {
					crs.add(result);
				}
			}
		}
		
		for (Entry<String, Instant> entry : fileList.entrySet()) {
			String p = entry.getKey();
			Date lastModifyTime = Date.from(Instant.now());
			CompareResult result = new CompareResult(p,lastModifyTime,OperateKind.DELETE);
			crs.add(result);
		}
		return crs;
	}

	public Path makeUpdatePackage(String fileList,Path packagesPath) throws Exception{
		String root = properties.getRoot();
		if (!Files.exists(packagesPath)) {
			Files.createDirectories(packagesPath);
		}
		Path pkgPath = Files.createTempDirectory(packagesPath, "package");
		ObjectMapper mapper = new ObjectMapper();
		List<CompareResult> list = mapper.readValue(fileList, CollectionType.construct(List.class, SimpleType.construct(CompareResult.class)));
		for (CompareResult result : list) {
			if (OperateKind.DELETE == result.getKind()) {
				continue;
			}
			String relativizePath = result.getPath();
			Path filePath = fs.getPath(root, relativizePath);
			Path targetPath = fs.getPath(pkgPath.toString(), relativizePath);
			if (!Files.exists(targetPath.getParent())) {
				Files.createDirectories(targetPath.getParent());
			}
			if (Files.exists(filePath)) {
				Files.copy( filePath,targetPath, StandardCopyOption.REPLACE_EXISTING ); 
			}
			
		}
		Path fileListPath = fs.getPath(pkgPath.toString(), "filelist.lst");
		try(Writer writer = new FileWriter(fileListPath.toFile())){
			IOUtils.write(fileList.getBytes(Consts.UTF_8), writer);
		}
		String timestamp = TIMESTAMP_FORMATTER.format(Instant.now());
		Path pkgFilePath = fs.getPath(packagesPath.toString(),"update"+timestamp+".zip" );
		CompressFileUtil.compress(pkgPath, packagesPath,"update"+timestamp+".zip");
		return Files.exists(pkgFilePath) ? pkgFilePath : null;
	}
}
